#include <linux/version.h>/* LINUX_VERSION_CODE KERNEL_VERSION()*/
#include <linux/init.h>   /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#include <linux/delay.h> /* ssleep */

#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#include <linux/gpio.h>

#include <linux/interrupt.h>

#include <linux/i2c.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

#include "./sdriver_ap3216c.h" // 包含了ap3216的寄存器定义

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

#define CHRDEV_NAME    ("sdev")    /* 设备名,cat /proc/devices 查看与设备号的对应关系 */
#define CLASS_NAME     ("sclass")  /* 类名，在 /sys/class 中显示的名称 */
#define DEVICE_NAME    ("ap3216c") /* 设备节点名，在 /sys/class/class_name/ 中显示的名称以及 /dev/ 下显示的节点名 */
#define BUFSIZE        (32)        /* 设置最大偏移量为32 */
#define DRIVER_NAME    ("ap3216c") /* 驱动名称，和设备名称相同时可以匹配成功 */
                                   /* 会在 /sys/bus/bus_name/drivers中创建对应目录*/
                                   /* 即/sys/bus/bus_name/drivers/driver-name*/

/* 中断IO描述结构体 */
typedef struct __irq_gpio_desc {
    char name[16];							/* 名字 */
	int gpio;								/* gpio */
	int irq_num;							/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
    unsigned long flags;                    /* 触发方式 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
}irq_gpio_desc_t;

typedef struct __ap3216c_data {
    unsigned short ir;  // 红外线强度(IR)
    unsigned short als; // 环境光强度(ALS)
    unsigned short ps;  // 接近距离(PS)
} ap3216c_data_t;       /* 三个光传感器数据 */

typedef struct __chr_device {
    char dev_name[32];     // 设备名称， /dev/dev-name
    dev_t dev_num;         // 定义dev_t类型（32位大小）的变量dev_num,用来存放设备号
    struct cdev s_cdev;    // 定义cdev结构体类型的变量scdev
    struct class *class;   // 定于struct class *类型结构体变量 class，表示要创建的类
    struct device *device; // 设备

    struct device_node *nd; // 设备节点

    irq_gpio_desc_t irq_key; // 按键的中断信息
    atomic_t key_value;      // 按键值 

    ap3216c_data_t ap3216c_data;
    void *private_data;      //私有数据
} chr_device_t;

static chr_device_t g_chrdev = {0}; // 定义一个device_test结构体变量

// ===== （5）一个按键中断 =====
/**
 * @brief imx6ull_key_read()
 * @note  读取key按键的值
 * @param [in] 
 * @param [out]
 * @retval 
 */
static int imx6ull_key_read(chr_device_t *p_chrdev)
{
    int value = 0xff;

    if (gpio_get_value(p_chrdev->irq_key.gpio) == 0)
    { 
        /* key0按下 */
        while (!gpio_get_value(p_chrdev->irq_key.gpio)); /* 等待按键释放 */
        atomic_set(&p_chrdev->key_value, 0); // 按键值设置为0
    }
    else
    {
        atomic_set(&p_chrdev->key_value, 0xff); /* 无效的按键值 */
    }

    value = atomic_read(&p_chrdev->key_value);

    return value;
}

/**
 * @brief gpio_irq_handler()
 * @note  中断处理函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    chr_device_t *p_chrdev=(chr_device_t *)dev_id;

    PRT("Interrupt occurred on [p_chrdev->irq_key GPIO %d irq %d], irq=%d\n", 
            p_chrdev->irq_key.gpio, p_chrdev->irq_key.irq_num, irq);
    return IRQ_HANDLED;
}

/**
 * @brief  gpio_irq_init()
 * @note   gpio中断初始化
 * @param  [in]
 * @param  [out]
 * @retval 
 */
static int gpio_irq_init(chr_device_t *p_chrdev)
{
    int ret = 0;
    if(p_chrdev == NULL)
    {
        return -1;
    }

    p_chrdev->nd = of_find_node_by_path("/alpha/sdev_int_key");
	if (p_chrdev->nd == NULL)
    {
		PRTE("key node not find!\n");
		return -EINVAL;
	}

    p_chrdev->irq_key.gpio = of_get_named_gpio(p_chrdev->nd, "key-gpio", 0);
    if (p_chrdev->irq_key.gpio < 0)
    {
        PRTE("can't get key %d\n", 0);
        return -EINVAL;
    }

    memset(p_chrdev->irq_key.name, 0, sizeof(p_chrdev->irq_key.name));	/* 缓冲区清零 */
	sprintf(p_chrdev->irq_key.name, "irq_key_%d", 0);		/* 组合名字 */
    
    p_chrdev->irq_key.handler = gpio_irq_handler;
    p_chrdev->irq_key.flags = IRQF_TRIGGER_RISING;
    
    p_chrdev->irq_key.irq_num = gpio_to_irq(p_chrdev->irq_key.gpio); // 将GPIO引脚映射到中断号
    PRT("GPIO %d mapped to IRQ %d\n", p_chrdev->irq_key.gpio, p_chrdev->irq_key.irq_num);

    // 请求中断
    ret = request_irq(p_chrdev->irq_key.irq_num, p_chrdev->irq_key.handler, 
                      p_chrdev->irq_key.flags, p_chrdev->irq_key.name, 
                      p_chrdev);
    if (ret != 0)
    {
        PRTE("Failed to request IRQ %d\n", p_chrdev->irq_key.irq_num);
        // 请求中断失败，释放GPIO引脚
        gpio_free(p_chrdev->irq_key.gpio);
        return -ENODEV;
    }

    return 0;
}

/**
 * @brief  gpio_irq_uninit()
 * @note   gpio中断去初始化
 * @param  [in]
 * @param  [out]
 * @retval 
 */
static int gpio_irq_uninit(chr_device_t *p_chrdev)
{
    // 释放中断
    free_irq(p_chrdev->irq_key.irq_num, p_chrdev);//这里的第二个参数一定要和request_irq()最后一个参数一致
    //释放GPIO引脚
    gpio_free(p_chrdev->irq_key.gpio);

    return 0;
}
// ===== （4）实现设备的实际读写操作 =====
/**
 * @brief  ap3216c_read_regs()
 * @note   从ap3216c读取多个寄存器数据
 * @param  [in] dev:  ap3216c设备
 * @param  [in] reg:  要读取的寄存器首地址
 * @param  [in] val:  读取到的数据
 * @param  [in] len:  要读取的数据长度
 * @param  [out] 
 * @retval 
 */
static int ap3216c_read_regs(chr_device_t *dev, char reg, void *val, int len)
{
	int ret = 0;
	struct i2c_msg msg[2] = {0};
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
    if (ret == 2)
    {
        ret = 0;
    }
    else
    {
        PRT("i2c rd failed=%d reg=%06x len=%d\n", ret, reg, len);
        ret = -EREMOTEIO;
    }
    return ret;
}

/**
 * @brief  ap3216c_write_regs()
 * @note   向ap3216c多个寄存器写入数据
 * @param  [in] dev:  ap3216c设备
 * @param  [in] reg:  要写入的寄存器首地址
 * @param  [in] val:  要写入的数据缓冲区
 * @param  [in] len:  要写入的数据长度
 * @param  [out]
 * @retval 
 */
static int ap3216c_write_regs(chr_device_t *dev, char reg, char *buf, char len)
{
	char b[256] = {0};
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/**
 * @brief  ap3216c_read_reg()
 * @note   读取ap3216c指定寄存器值，读取一个寄存器
 * @param  [in] dev:  ap3216c设备
 * @param  [in] reg:  要读取的寄存器
 * @param  [out]
 * @retval 
 */
static unsigned char ap3216c_read_reg(chr_device_t *dev, char reg)
{
	char data = 0;

	ap3216c_read_regs(dev, reg, &data, 1);

	return data;

#if 0
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	return i2c_smbus_read_byte_data(client, reg);
#endif
}

/**
 * @brief  
 * @note   向ap3216c指定寄存器写入指定的值，写一个寄存器
 * @param  [in] dev:  ap3216c设备
 * @param  [in] reg:  要写的寄存器
 * @param  [in] data: 要写入的值
 * @param  [out]
 * @retval 
 */
static void ap3216c_write_reg(chr_device_t *dev, char reg, char data)
{
	char buf = 0;
	buf = data;
	ap3216c_write_regs(dev, reg, &buf, 1);
}

/**
 * @brief  ap3216c_read_data()
 * @note   读取AP3216C的数据，读取原始数据，包括ALS,PS和IR, 注意！
 *         如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param  [in] ir	: ir数据
 * @param  [in] ps 	: ps数据
 * @param  [in] ps 	: als数据 
 * @param  [out]
 * @retval 
 */
static void ap3216c_read_data(chr_device_t *dev)
{
	unsigned char i =0;
    unsigned char buf[6];

    /* 循环读取所有传感器数据 */
    for (i = 0; i < 6; i++)
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
    }

    if (buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
    {
        dev->ap3216c_data.ir = 0;
    }
    else /* 读取IR传感器的数据 */
    {
        dev->ap3216c_data.ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
    }

    dev->ap3216c_data.als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 			 */

    if (buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
    {
        dev->ap3216c_data.ps = 0;
    }
    else /* 读取PS传感器的数据    */
    {
        dev->ap3216c_data.ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
    }

    return;
}

// ===== （3）实现字符设备操作函数集 =====
static int scdev_ops_open(struct inode *pInode, struct file *pFile)
{
    //PRT("This is scdev_ops_open!dev name=%s\n", g_chrdev.dev_name);
    pFile->private_data = &g_chrdev;  // 设置私有数据

    /* 初始化AP3216C */
	ap3216c_write_reg(&g_chrdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&g_chrdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/

    return 0;
}

static ssize_t scdev_ops_read(struct file *pFile, char __user *buf, size_t size, loff_t *off)
{
    size_t count = size;
    short data[3] = {0};

	chr_device_t *p_chrdev=(chr_device_t *)pFile->private_data; //在write函数中读取private_data

	ap3216c_read_data(p_chrdev);

	data[0] = p_chrdev->ap3216c_data.ir;
	data[1] = p_chrdev->ap3216c_data.als;
	data[2] = p_chrdev->ap3216c_data.ps;

	if (copy_to_user(buf, data, sizeof(data)))
    {
        PRT("copy_to_user error!\n");
        return -1;
    }

    *off = *off + size; // 更新偏移值
    return count;
}

static ssize_t scdev_ops_write(struct file *pFile, const char __user *buf, size_t size, loff_t *off)
{
    size_t count = size;
    //chr_device_t *p_chrdev=(chr_device_t *)pFile->private_data; //在write函数中读取private_data

    *off = *off + count; // 更新偏移值

    return count;

}

static int scdev_ops_release(struct inode *pInode, struct file *pFile)
{
    //chr_device_t *p_chrdev=(chr_device_t *)pFile->private_data; //在write函数中读取private_data
    //PRT("This is scdev_ops_release!dev_name=%s\n", p_chrdev->dev_name);
    return 0;
}

static loff_t scdev_ops_llseek(struct file *pFile, loff_t offset, int whence)
{
    loff_t new_offset = 0; // 定义loff_t类型的新的偏移值
    switch (whence)    // 对lseek函数传递的whence参数进行判断
    {
        case SEEK_SET:
            if (offset < 0 || offset > BUFSIZE)
            {
                return -EINVAL; // EINVAL=22 表示无效参数
            }
            new_offset = offset; // 如果whence参数为SEEK_SET，则新偏移值为offset
            break;
        case SEEK_CUR:
            if ((pFile->f_pos + offset < 0) || (pFile->f_pos + offset > BUFSIZE))
            {
                return -EINVAL;
            }
            new_offset = pFile->f_pos + offset; // 如果whence参数为SEEK_CUR，则新偏移值为pFile->f_pos + offset，pFile->f_pos为当前的偏移值
            break;
        case SEEK_END:
            if (pFile->f_pos + offset < 0)
            {
                return -EINVAL;
            }
            new_offset = BUFSIZE + offset; // 如果whence参数为SEEK_END，则新偏移值为BUFSIZE + offset，BUFSIZE为最大偏移量
            break;
        default:
            break;
    }
    pFile->f_pos = new_offset; // 更新pFile->f_pos偏移值

    return new_offset;
}

static struct file_operations g_scdev_ops = {
    .owner = THIS_MODULE, // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    .open = scdev_ops_open,
    .read = scdev_ops_read,
    .write = scdev_ops_write,
    .release = scdev_ops_release,
    .llseek = scdev_ops_llseek,
}; // 定义file_operations结构体类型的变量g_cdev_dev_ops

static int scdev_create(chr_device_t *p_chrdev)
{
    int ret;          // 定义int类型的变量ret，用来判断函数返回值
    int major, minor; // 定义int类型的主设备号major和次设备号minor
    /* 1、构建设备号 */
    ret = alloc_chrdev_region(&p_chrdev->dev_num, 0, 1, CHRDEV_NAME); // 自动获取设备号，设备名为chrdev_name
    if (ret < 0)
    {
        PRTE("alloc_chrdev_region is error!ret=%d\n", ret);
        goto err_alloc_devno;
    }
    major = MAJOR(p_chrdev->dev_num); // 使用MAJOR()函数获取主设备号
    minor = MINOR(p_chrdev->dev_num); // 使用MINOR()函数获取次设备号
    //PRT("major is %d, minor is %d !\n", major, minor);
    /* 2、注册设备 */
    cdev_init(&p_chrdev->s_cdev, &g_scdev_ops); // 使用cdev_init()函数初始化p_chrdev->s_cdev结构体，并链接到 cdev_ops 结构体
    p_chrdev->s_cdev.owner = THIS_MODULE;          // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    ret = cdev_add(&p_chrdev->s_cdev, p_chrdev->dev_num, 1); // 使用cdev_add()函数进行字符设备的添加
    if (ret < 0)
    {
        PRTE("cdev_add is error !ret=%d\n", ret);
        goto err_cdev_add;
    }
    /* 3、创建类 */
    p_chrdev->class = class_create(THIS_MODULE, CLASS_NAME); // 使用class_create进行类的创建，类名称为 class_dev
    if(IS_ERR(p_chrdev->class))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_class_create;
    }
    /* 4、创建设备 */
    p_chrdev->device = device_create(p_chrdev->class, NULL, p_chrdev->dev_num, NULL, "%s", DEVICE_NAME); // 使用device_create进行设备的创建，设备名称为 device_dev
    if(IS_ERR(p_chrdev->device))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_device_create;
    }
    snprintf (p_chrdev->dev_name, sizeof(p_chrdev->dev_name), "/dev/%s", DEVICE_NAME);

    //PRT("scdev_create %s success!\n", p_chrdev->dev_name);
    return 0;
    // 错误处理
err_device_create:
    class_destroy(p_chrdev->class);   // 删除创建的类
err_class_create:
    cdev_del(&p_chrdev->s_cdev);      // 使用cdev_del()函数进行字符设备的删除
err_cdev_add:
    unregister_chrdev_region(p_chrdev->dev_num, 1); //注销设备号
err_alloc_devno:
    return ret;
}

static void scdev_destroy(chr_device_t *p_chrdev)
{
    // 需要注意的是， 字符设备的注册要放在申请字符设备号之后，
    // 字符设备的删除要放在释放字符驱动设备号之前。
    cdev_del(&p_chrdev->s_cdev);                        // 使用cdev_del()函数进行字符设备的删除
    unregister_chrdev_region(p_chrdev->dev_num, 1);     // 释放字符驱动设备号
    device_destroy(p_chrdev->class, p_chrdev->dev_num); // 删除创建的设备
    class_destroy(p_chrdev->class);                     // 删除创建的类
    // PRT("scdev_destroy success!\n");
}

// ===== （2）.probe函数和.remove函数实现 =====
/**
 * @brief  sdrv_i2c_driver_probe()
 * @note   i2c驱动的probe函数，当驱动与设备匹配以后此函数就会执行
 * @param  [in]
 * @param  [in] client  : i2c设备
*  @param  [in] id      : i2c设备ID
 * @param  [out]
 * @retval 
 */
static int sdrv_i2c_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
    PRT("probing i2c_client & i2c_driver!client->name=%s\n", client->name);
	// 注册设备
    ret = scdev_create(&g_chrdev);
    if (ret < 0) 
    {
        PRTE("Failed to scdev_create!ret=%d\n", ret);
        goto err_scdev_create;
    }

	g_chrdev.private_data = client;

	return 0;
err_scdev_create:
    return ret;
}

/**
 * @brief  sdrv_i2c_driver_remove()
 * @note   i2c驱动的remove函数，移除i2c驱动的时候此函数会执行
 * @param  [in] client 	: i2c设备
 * @param  [out]
 * @retval 
 */
static int sdrv_i2c_driver_remove(struct i2c_client *client)
{
    PRT("removing i2c_driver!client->name=%s\n", client->name);
	// 注销设备
	scdev_destroy(&g_chrdev);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id sdrv_i2c_driver_id[] = {
	{"sdev,ap3216c", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id sdrv_i2c_driver_of_match[] = {
	{ .compatible = "sdev,ap3216c" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */
static struct i2c_driver g_sdrv_i2c_driver = {
    .probe = sdrv_i2c_driver_probe,
    .remove = sdrv_i2c_driver_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = DRIVER_NAME,
        .of_match_table = sdrv_i2c_driver_of_match,
    },
    .id_table = sdrv_i2c_driver_id,
};

// =====（1）驱动入口和出口函数实现 =====
/**
 * @brief  sdrv_demo_init()
 * @note   注册驱动
 * @param  [in]
 * @param  [out]
 * @retval 
 */
static __init int sdrv_demo_init(void)
{
    int ret = 0;
	printk("*** [%s:%d]Build Time: %s %s, git version:%s LINUX_VERSION=%d.%d.%d ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION, 
           (LINUX_VERSION_CODE >> 16) & 0xff, (LINUX_VERSION_CODE >> 8) & 0xff, LINUX_VERSION_CODE & 0xff);

    ret = i2c_add_driver(&g_sdrv_i2c_driver);
    if(ret != 0)
    {
        PRTE("i2c_add_driver fail!ret=%d\n", ret);
        goto err_i2c_add_driver;
    }
    //PRT("sdrv_demo module init success!\n");
	return 0;

err_i2c_add_driver:
    return ret;
}

/**
 * @brief  sdrv_demo_exit()
 * @note   注销驱动
 * @param  [in]
 * @param  [out]
 * @retval 
 */
static __exit void sdrv_demo_exit(void)
{
    i2c_del_driver(&g_sdrv_i2c_driver);
    //PRT("sdrv_demo module exit!\n");
}

module_init(sdrv_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo xxx.ko 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */
